/*
* @Author: z.p
* @Project: Zdotp
* @Date: 2024年12月31日
* @Description: 前端接口
*/

using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;

namespace Zdotp.Net.Kcp.App;

public sealed class ClientApp : ZyKcpApp
{
    private KcpSession _clientSession;

    protected override void SendUdpMessage(byte[] bytes, IPEndPoint remotePoint)
    {
        base.SendUdpMessage(bytes, remotePoint);
        // 包头长度(24)+PROTOCOL(8)
        if (bytes.Length == 32)
        {
            CloseSelf();
        }
    }

    private bool IsConnect()
    {
        return _clientSession != null && _clientSession.IsConnected();
    }

    public override void Start(string ip, int port) 
    {
        base.Start(ip, port);
        Udp = new UdpClient(0);
        if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 
        {
            Udp.Client.IOControl((IOControlCode)(-1744830452), "\0\0\0\0"u8.ToArray(), null);
        }
        
        RemotePoint = new IPEndPoint(IPAddress.Parse(ip), port);

        Task.Run(ClientReceive, Cts.Token);
    }

    public void SendMsgToServer(uint protocolId, byte[] msg)
    {
        if (!IsConnect())
        {
            KcpLogger.Warn("ClientSession is null or disconnected.");
            return;
        }
        var sessionIdBytes = BitConverter.GetBytes(_clientSession.SessionId);
        var protocolIdBytes = BitConverter.GetBytes(protocolId);
        byte[] result;
        if (protocolId != 0)
        {
            result = new byte[8 + msg.Length];
            Array.Copy(sessionIdBytes, 0, result, 0, 4);
            Array.Copy(protocolIdBytes, 0, result, 4, 4);
            Array.Copy(msg, 0, result, 8, msg.Length);
        }
        else
        {
            result = new byte[8];
            Array.Copy(sessionIdBytes, 0, result, 0, 4);
            Array.Copy(protocolIdBytes, 0, result, 4, 4);
        }
        _clientSession.SendMessage(result);
    } 
    
    public Task<bool> ClientConnectServer(float interval, float maxInterval = 3f) 
    {
        if (IsConnect())
        {
            return Task.FromResult(false);
        }
        SendUdpMessage(new byte[4], RemotePoint);
        var checkTimes = 0f;
        var task = Task.Run(async () => 
        {
            while(true) 
            {
                await Task.Delay(TimeSpan.FromSeconds(interval), Cts.Token);
                checkTimes += interval;
                if(IsConnect()) 
                {
                    return true;
                }
                if(checkTimes > maxInterval) 
                {
                    return false;
                }
            }
        }, Cts.Token);
        return task;
    }

    private async void ClientReceive()
    {
        try 
        {
            while(true) 
            {
                var result = await Udp.ReceiveAsync(Cts.Token);

                if(Equals(RemotePoint, result.RemoteEndPoint))
                {
                    if (IsConnect())
                    {
                        _clientSession.ReceiveData(result.Buffer);
                    }
                    else
                    {
                        var sessionId = BitConverter.ToUInt32(result.Buffer, 0);
                        if (sessionId == 0 || result.Buffer.Length != 4)
                        {
                            KcpLogger.Warn("Client is fail.");
                            continue;
                        }
                        _clientSession = new KcpSession(this, sessionId, SendUdpMessage, RemotePoint);
                        _clientSession.SessionStart();
                    }
                }
                else 
                {
                    KcpLogger.Warn("Client Udp Receive illegal target Data.");
                }
            }
        }
        catch(Exception e) 
        {
            KcpLogger.Warn($"Client Udp Receive Data Exception : {e}");
        }
    }

    public override void Close()
    {
        SendMsgToServer(0, new byte[1]);
    }

    private void CloseSelf()
    {
        _clientSession?.CloseSession();
        Cts?.Cancel();
        Udp?.Close();
    }
}